#include<iostream>
#include<string>
#include<ctype.h>
#include<cstdio>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/wait.h>
#include<cstdlib>
#include<cstring>
using namespace std;
#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "
#define MAXARGC 128
#define MAXENVS 100

#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3  
char* g_argv[MAXARGC];
char* g_env[MAXENVS];
int g_envs = 0;
int g_argc = 0;
char cwd[1024];
char cwdenv[1024];
int lastcode = 0; //最近一个进程的退出码
//1.使用环境变量打印命令行的提示字符串

int redir = NONE_REDIR;
string filename;
const char* GetUserName()
{
  const char* name = getenv("USER");
  return name == NULL ? "None" : name;
}
const char* GetHostName()
{
  const char* hostname = getenv("HOSTNAME");
  return hostname == NULL ? "None" : hostname;
}
const char* GetPwd()
{
  //const char* pwd = getenv("PWD");  //进程的当前工作改变了，才能更新环境变量，所以这里直接用环境变量来获得工作路径不合适
  const char* pwd = getcwd(cwd, sizeof(cwd)); //系统调用接口来获取进程的当前工作路径
  if(pwd != NULL) //获取当前工作路径成功
  {
    snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd);
    putenv(cwdenv);
  }
  return pwd == NULL ? "None" : pwd;
}
const char* GetHome()
{
  const char* home = getenv("HOME");
  return home == NULL ? "" : home;
}
void InitEnv()
{
  extern char** environ; //全局的系统环境变量表的指针
  memset(g_env, 0, sizeof(g_env));
  g_envs = 0;
  for(int i = 0; environ[i]; i++)
  {
    //给自己的环境变量表申请空间
    g_env[i] = (char*)malloc(strlen(environ[i]) + 1);
    strcpy(g_env[i], environ[i]);
    g_envs++;
  }
  g_env[g_envs] = NULL; //环境变量表和命令行参数表一样，最后一个元素是NULL
}
string DirName(const char* pwd)
{
#define SLASH "/"
  string dir = pwd;
  if(dir == SLASH) return SLASH;
  auto pos = dir.rfind(SLASH);
  if(pos == string::npos) return "BUG";
  return dir.substr(pos + 1);
}
void MakeCommandLine(char cmd_prompt[], int size)
{
   snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), DirName(GetPwd()).c_str());
}
void PrintCommandPrompt()
{
  char Prompt[COMMAND_SIZE];
  MakeCommandLine(Prompt, sizeof(Prompt));
  printf("%s", Prompt);
  fflush(stdout); //刷新缓冲区，让他立即打印出来
}
bool GetCommandLine(char* out, int size)
{
  char* c = fgets(out, size, stdin);
  if(c == NULL) return 1; //读取输入文件失败了
  out[strlen(out) - 1] = 0; //清理\n
  if(strlen(out) == 0) return false;
  return true;
}
bool CommandPrase(char* commandline)
{
#define SEP " "
  g_argc = 0; //每次命令行分割解析都把命令行参数个数设置0
  g_argv[g_argc++] = strtok(commandline, SEP);
  while((bool)(g_argv[g_argc++] = strtok(nullptr, SEP)));
  g_argc--; //不要指令这个参数 如ls -a , 不要ls
  return true;
}
bool Cd()
{
  if(g_argc == 1)
  {
    string home = GetHome();  //命令行只有cd，表示切换到家目录
    if(home.empty()) return true; //路径是空的，不用cd到某个路径
    chdir(home.c_str()); //使用系统调用接口修改路径
  }
  else 
  {
    string where = g_argv[1];
    if(where == "-")
    {
        //TODO 最近切换的路径
    }
    else if(where == "~")
    {
      //todo 切换到家目录
    }
    else 
    {
      chdir(where.c_str());
    }
  }
  return true;
}
void Echo()
{
  if(g_argc == 2)
  {
    string opt = g_argv[1];
    if(opt == "$?")
    {
      cout << lastcode << endl;
      lastcode = 0; //执行完最近获得最近进程的退出码命令后要对最近退出码清0

    }
    else if(opt[0] == '$') //打印环境变量的内容
    {
      string env_name = opt.substr(1);
      const char* env_value =  getenv(env_name.c_str());
      if(env_value)
      {
        cout << env_value << endl;
      }
    }
    else 
    {
      cout << opt << endl;
    }
  }
}
bool CheckAndExeBuiltin() //检查是否是内建命令
{
    string cmd = g_argv[0];
    if(cmd == "cd")
    {
      Cd();
      return true;
    }
    else if(cmd == "echo")
    {
      Echo();
      return true;
    }
    return false; //不是内建命令，要进行创建子进程执行
}
void TrimSpace(char cmd[], int& end)
{
  while(isspace(cmd[end]))
  {
    end++;
  }
}
void RedirCheck(char cmd[])
{
  redir = NONE_REDIR;
  filename.clear(); //保证每次重定向指令操作都是最新的状态和文件名
  int start = 0;
  int end = strlen(cmd) - 1;
  while(end > start)
  {
    if(cmd[end] == '<') //重定向输入
    {
      cmd[end++] = 0; //设置0覆盖重定向符号，让后面指令好执行
      TrimSpace(cmd, end); //跳过重定向符号<右边的文件名空格，前面的指令空格不管,strtok不会提取空串
      redir = INPUT_REDIR;
      filename = cmd + end;
      break;
    }
    else if(cmd[end] == '>') //重定向输出
    {
      if(cmd[end - 1] == '>')
      {
        cmd[end - 1] = 0;
        redir = APPEND_REDIR;
      }
      else 
      {
        redir = OUTPUT_REDIR;
      }
      cmd[end++] = 0;
      TrimSpace(cmd, end);
      filename = cmd + end;
      break;
    }
    else 
    {
      end--;
    }
  }
}
int Execute()
{
  pid_t id = fork();
  if(id == 0) //子进程
  {
     int fd = -1;
     if(redir == INPUT_REDIR) //输入重定向
     {
       fd = open(filename.c_str(), O_RDONLY); 
       if(fd < 0) exit(1); //打开文件失败
       dup2(fd, 0); //更新默认从stdin文件读改成从我们打开的fd位置的文件读
       close(fd);
     } 
     else if(redir == OUTPUT_REDIR)
     {
       fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0666); //默认权限掩码
       if(fd < 0) exit(2);
       dup2(fd, 1);
       close(fd);
     }
     else if(redir == APPEND_REDIR)
     {
       fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666); //默认创建文件权限的权限掩码
       if(fd < 0) exit(3);
       dup2(fd, 1);
       close(fd);
     }
      execvp(g_argv[0], g_argv);
      exit(1);
  }
  int status = 0;
  pid_t rid = waitpid(id, &status, 0); //第3个参数设置阻塞式等待
  if(rid > 0) //进程等待成功
  {
    lastcode = WEXITSTATUS(status); //获取最近一个进程的退出码
  }
  return 0;
}
int main()
{
  // shell 启动的时候，从系统中获取环境变量
  // 我们的环境变量信息应该从父shell统一来
  InitEnv();
  while(true)
  { 
    //1.输出命令行提示符
    PrintCommandPrompt();
    //2.获取用户输入的命令
    char commandline[COMMAND_SIZE];
    if(!GetCommandLine(commandline, sizeof(commandline))) //没输入命令则一直打印命令提示符
    {
       continue;
    }
    //3. 重定向分析判断
    RedirCheck(commandline);
    //4.分割命令行参数进行命令行解析
    CommandPrase(commandline);
    //5.检查是否是内建命令，如果是不能创建子进程来执行，要由父进程亲自执行
   if(CheckAndExeBuiltin())
   {
     continue;
   }
    //6.创建子进程程序替换执行命令
    Execute();
  }
  return 0;
}
